using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Felbot.Protocol;

namespace Felbot.Interface.Users {
    /// <summary>This list controls a list of users in the friend list and friend events.</summary>
    public class Friends {
        #region Private Fields and Delegates
        private List<FriendUser> m_Users = new List<FriendUser>(25);
        private ListView m_lv;
        private Label m_lbl;
        private frmProfile m_frmProfile;

        private delegate void FriendsDelegate();
        #endregion

        #region External Friend List Properties and Methods
        /// <summary>
        /// Gets the list of users in the friend list.
        /// </summary>
        public List<FriendUser> Users {
            get {
                return m_Users;
            }
        }

        /// <summary>
        /// Gets the list of users in the friend list who are online.
        /// </summary>
        public List<FriendUser> Online {
            get {
                List<FriendUser> online = new List<FriendUser>();
                foreach (FriendUser friend in m_Users)
                    if (friend.Online)
                        online.Add(friend);
                return online;
            }
        }

        /// <summary>
        /// Gets the list of users in the friend list who are online and mutual.
        /// </summary>
        public List<FriendUser> Mutual {
            get {
                List<FriendUser> mutual = new List<FriendUser>();
                foreach (FriendUser friend in m_Users)
                    if (friend.Online && friend.Status == BattleNetFriendStates.Mutual)
                        mutual.Add(friend);
                return mutual;
            }
        }

        /// <summary>
        /// Gets a user in the friend list as a FriendListItem by their username.
        /// </summary>
        /// <param name="a_sUsername">Their username.</param>
        /// <returns>A FriendListITem for the user.</returns>
        public FriendUser GetUser(string username) {
            foreach (FriendUser user in Users)
                if (user.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase))
                    return user;
            return null;
        }
        #endregion

        /// <summary>Creates a new instance of this class.</summary>
        /// <param name="main">The profile form that this is associated with.</param>
        public Friends(frmProfile main, ListView listview, Label label) {
            m_frmProfile = main;
            m_lv = listview;
            m_lbl = label;
        }

        #region Friend Information Changed
        /// <summary>
        /// Called on SID_FRIENDSLIST, adds a user to the list.
        /// </summary>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_bStatus">Status</param>
        /// <param name="a_bLocation">Location</param>
        /// <param name="a_bStatus">Status (mutual/away/dnd)</param>
        /// <param name="a_sProduct">Product ID</param>
        /// <param name="a_sLocation">Location string</param>
        public void RecievedFriend(string username, byte status, byte location, string product, string locationName) {
            FriendUser user = new FriendUser(username, status, location, product, locationName);
            m_Users.Add(user);
            m_frmProfile.Invoke((FriendsDelegate) delegate() {
                m_lv.Items.Add(GetListViewItem(user));
            });
        }

        /// <summary>
        /// Called when SID_FRIENDSLIST has completed adding users to the list with FriendsList(), changes the header
        /// </summary>
        public void RecievedFriendList() {
            m_frmProfile.Invoke((FriendsDelegate) delegate() {
                UpdateHeader();
            });
        }

        /// <summary>
        /// Called on SID_FRIENDSADD, adds a user to the list.
        /// </summary>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_bStatus">Status</param>
        /// <param name="a_bLocation">Location</param>
        /// <param name="a_bStatus">Status (mutual/away/dnd)</param>
        /// <param name="a_sProduct">Product ID</param>
        /// <param name="a_sLocation">Location string</param>
        public void AddFriend(string username, byte status, byte location, string product, string locationName) {
            FriendUser user = new FriendUser(username, status, location, product, locationName);
            m_Users.Add(user);
            m_frmProfile.Invoke((FriendsDelegate) delegate() {
                m_lv.Items.Add(GetListViewItem(user));
                UpdateHeader();
            });
        }

        /// <summary>
        /// This is called when SID_FRIENDSREMOVE occurs.
        /// </summary>
        /// <param name="a_bPosition">Position of username to remove</param>
        public void RemoveFriend(int position) {
            if (m_Users.Count > position) {
                m_Users.RemoveAt(position);
                m_frmProfile.Invoke((FriendsDelegate) delegate() {
                    m_lv.Items.RemoveAt(position);
                    UpdateHeader();
                });
            }
        }

        /// <summary>
        /// This method is called when SID_FRIENDSUPDATE occurs.
        /// </summary>
        /// <param name="a_bPosition">Position</param>
        /// <param name="a_bStatus">Status</param>
        /// <param name="a_bLocation">Location</param>
        /// <param name="a_bStatus">Status (mutual/away/dnd)</param>
        /// <param name="a_sProduct">Product ID</param>
        /// <param name="a_sLocation">Location string</param>
        public void UpdateFriend(byte position, byte status, byte location, string product, string locationName) {
            if (m_Users.Count > position) {
                if (m_Users[position].Status == (BattleNetFriendStates) status &&
                    m_Users[position].Location == (BattleNetFriendLocations) location &&
                    m_Users[position].Product == product &&
                    m_Users[position].LocationName == locationName)
                    return;

                m_Users[position].Status = (BattleNetFriendStates) status;
                m_Users[position].Location = (BattleNetFriendLocations) location;
                m_Users[position].Product = product;
                m_Users[position].LocationName = locationName;
                m_frmProfile.Invoke((FriendsDelegate) delegate() {
                    m_lv.Items[position] = GetListViewItem(m_Users[position]);
                });
            }
        }

        /// <summary>
        /// This method is called when SID_FRIENDSPOSITION occurs.
        /// </summary>
        /// <param name="a_iOldPosition">The old position of the user.</param>
        /// <param name="a_iNewPosition">The new position of the user.</param>
        public void MoveFriend(int oldPosition, int newPosition) {
            if (m_Users.Count > oldPosition && m_Users.Count > newPosition) {
                FriendUser tempUser = m_Users[oldPosition];
                m_Users.RemoveAt(oldPosition);
                m_Users.Insert(newPosition, tempUser);
                m_frmProfile.Invoke((FriendsDelegate) delegate() {
                    ListViewItem m_lvItem = m_lv.Items[oldPosition];
                    m_lv.Items.RemoveAt(oldPosition);
                    m_lv.Items.Insert(newPosition, m_lvItem);
                });
            }
        }
        #endregion

        #region Friend List Header
        /// <summary>
        /// Changes the friend list header and form text when list changes.
        /// </summary>
        private void UpdateHeader() {
            m_lbl.Text = " - " + m_Users.Count.ToString() + " Friends - ";

            StringBuilder tooltip = new StringBuilder();
            tooltip.AppendLine("Friends: " + Users.Count);
            tooltip.AppendLine("Online: " + Online.Count);
            tooltip.AppendLine("Mutual: " + Mutual.Count);
            m_lbl.Tag = (object) tooltip.ToString().Trim(Environment.NewLine.ToCharArray());
        }

        /// <summary>
        /// Changes the friend list header and form text when list changes.
        /// </summary>
        private void UpdateHeader(string text) {
            m_lbl.Text = " - " + text + " - ";

            m_lbl.Tag = (object) text;
        }

        /// <summary>
        /// Cleans up the friend list when you log off.
        /// </summary>
        public void Disconnect() {
            m_frmProfile.Invoke((FriendsDelegate) delegate() {
                m_lv.Items.Clear();
                m_Users.Clear();
                UpdateHeader("Offline");
            });
        }
        #endregion

        #region Friend List Item
        private ListViewItem GetListViewItem(FriendUser user) {
            ListViewItem m_lvItem = new ListViewItem();

            string locationString = "Offline";
            string statusString = "0x0 (Normal)";
            string product = user.Product;

            switch ((BattleNetFriendLocations) user.Location) {
                default:
                case BattleNetFriendLocations.Offline:
                    locationString = "Offline";
                    break;
                case BattleNetFriendLocations.NotInChat:
                    locationString = "Not in Chat";
                    break;
                case BattleNetFriendLocations.InChannel:
                    if (user.LocationName != "")
                        locationString = "In public channel " + user.LocationName;
                    else
                        locationString = "In a private channel";
                    break;
                case BattleNetFriendLocations.InPublicGame:
                    if (user.LocationName != "")
                        locationString = "In public game " + user.LocationName;
                    else
                        locationString = "In a public game";
                    break;
                case BattleNetFriendLocations.InPrivateGame:
                case BattleNetFriendLocations.InPrivateGame2:
                    if (user.LocationName != "")
                        locationString = "In private game " + user.LocationName;
                    else
                        locationString = "In a private game";
                    break;
            }

            statusString = "0x" + user.Status.ToString("X") + " (" + ((BattleNetFriendStates) user.Status).ToString() + ")";

            StringBuilder tooltip = new StringBuilder();
            tooltip.AppendLine("Location: " + locationString);
            if (user.Location != 0x00) {
                tooltip.AppendLine("Status: " + statusString);
                tooltip.AppendLine("Product: " + BattleNetClient.GetProductName(product));
            }
            m_lvItem.ToolTipText = tooltip.ToString().Trim(Environment.NewLine.ToCharArray());

            if (user.Location == 0x00)
                m_lvItem.ForeColor = Color.Gray;
            else
                m_lvItem.ForeColor = Color.White;

            if ((user.Status & BattleNetFriendStates.Mutual) == BattleNetFriendStates.Mutual)
                m_lvItem.Font = new Font(m_lvItem.Font, FontStyle.Bold);

            m_lvItem.Name = user.Username;
            m_lvItem.Text = user.Username;
            m_lvItem.UseItemStyleForSubItems = false;
            m_lvItem.ImageIndex = SelectIcon(user.Product);

            return m_lvItem;
        }

        /// <summary>
        /// This returns the iconindex for use with the specified product.
        /// </summary>
        /// <param name="a_sProduct">Product of user, in g_clUsers</param>
        /// <returns>IconIndex in imgListIcons of frmProfile</returns>
        private int SelectIcon(string product) {
            switch (product) {
                case "SSHR":
                case "JSTR":
                case "STAR":
                    return 2;
                case "SEXP":
                    return 3;
                case "DSHR":
                case "DRTL":
                    return 4;
                case "D2DV":
                    return 5;
                case "D2XP":
                    return 6;
                case "W2BN":
                    return 7;
                case "WAR3":
                    return 8;
                case "W3XP":
                    return 9;
                case "CHAT":
                    return 1;
                default:
                    return 0;
            }
        }
        #endregion
    }

    /// <summary>Represents a user in the friend list.</summary>
    public class FriendUser {
        private string m_Username;
        private byte m_Status;
        private byte m_Location;
        private string m_Product;
        private string m_LocationName;

        /// <summary>Gets their username.</summary>
        public string Username {
            get {
                return m_Username;
            }
            internal set {
                m_Username = value;
            }
        }

        /// <summary>Gets their status.</summary>
        public BattleNetFriendStates Status {
            get {
                return (BattleNetFriendStates) m_Status;
            }
            internal set {
                m_Status = (byte) value;
            }
        }
        /// <summary>Gets their location.</summary>
        public BattleNetFriendLocations Location {
            get {
                return (BattleNetFriendLocations) m_Location;
            }
            internal set {
                m_Location = (byte) value;
            }
        }

        /// <summary>Gets their product.</summary>
        public string Product {
            get {
                return m_Product;
            }
            internal set {
                m_Product = value;
            }
        }

        /// <summary>Gets their location by name.</summary>
        public string LocationName {
            get {
                return m_LocationName;
            }
            internal set {
                m_LocationName = value;
            }
        }

        /// <summary>Gets whether the user is online.</summary>
        public bool Online {
            get {
                return (Location != BattleNetFriendLocations.Offline);
            }
        }

        /// <summary>Creates a new friend list item.</summary>
        /// <param name="a_sUsername">Their username.</param>
        /// <param name="a_bStatus">Their status.</param>
        /// <param name="a_bLocation">Their location.</param>
        /// <param name="a_sProduct">Their product string.</param>
        /// <param name="a_sLocation">Their location string.</param>
        public FriendUser(string username, byte status, byte location, string product, string locationName) {
            m_Username = username;
            m_Status = status;
            m_Location = location;
            m_Product = product;
            m_LocationName = locationName;
        }
    }

    /// <summary>
    /// Specifies states for Battle.net friends.
    /// </summary>
    public enum BattleNetFriendStates : byte {
        /// <summary>This friend has no state flags.</summary>
        Normal = 0x00,
        /// <summary>This friend is a mutual friend.</summary>
        Mutual = 0x01,
        /// <summary>This friend is marked away.</summary>
        Away = 0x02,
        /// <summary>This friend is marked do not disturb.</summary>
        DoNotDisturb = 0x04
    }

    /// <summary>
    /// Specifies locations for Battle.net friends.
    /// </summary>
    public enum BattleNetFriendLocations : byte {
        /// <summary>This friend is offline.</summary>
        Offline = 0x00,
        /// <summary>This friend is not in chat.</summary>
        NotInChat = 0x01,
        /// <summary>This friend is in a channel.</summary>
        InChannel = 0x02,
        /// <summary>This friend is in a public game.</summary>
        InPublicGame = 0x03,
        /// <summary>This friend is in a private game.</summary>
        InPrivateGame = 0x04,
        /// <summary>This friend is in a private game.</summary>
        InPrivateGame2 = 0x05
    }
}
